package com.lizbet.server;


import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.Calendar;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.crypto.Cipher;
import javax.security.cert.CertificateException;
import javax.security.cert.X509Certificate;

import org.apache.commons.ssl.PKCS8Key;

import com.lbs.core.server.Base64;


//import sun.security.pkcs.PKCS8Key;


/**
 * Libreria con la criptografia necesario para la generacion de facturas electronicas, asi como la manipulacion del Certificado de Sello Digital
 *
 * @author Alberto Sandoval Sotelo 20/Agosto/2010
 */
public class LbsCrypt {
   private PrivateKey private_key;
   private PublicKey public_key;
   private X509Certificate cert;
   private String certNo="";
   byte[] certificado;

   /**
    * Constructor principal. Crea un objeto LbsCrypt, a partir de un .Key, un .Cer y una contraseña, correspondientes al Certificado de Sello Digital.
    *  
    * 
    * @param isKey Inputsretam al archivo .Key del Certificado de Sello Digital
    * @param Pass Contraseña del .key
    * @param isCer Inputstream al archivo .cer del Certificado de Sello Digital
    * 
    * @throws GeneralSecurityException Al no poder des-encriptar el archivo .key. Puede ser debido a que la contraseña sea invalida o el archivo en si se invalido
    * @throws IOException Al no encontrar alguno de los archivos o no poder acceder a el.
    * @throws CertificateException Error al procesar el Certificado. Probablemente por errores en el archivo.
    */
   public LbsCrypt(InputStream isKey, String Pass, InputStream isCer) throws GeneralSecurityException, IOException, CertificateException{
    PKCS8Key pkcs8 = null;
    	try{
        pkcs8 = new PKCS8Key(isKey, Pass.toCharArray());
    	}catch(Exception e){
    		throw new RuntimeException("Error al des-encriptar la llave privada");
    	}
        
        byte[] decrypted = pkcs8.getDecryptedBytes();
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec( decrypted );

        PrivateKey pk = null;
        if (pkcs8.isDSA()){
          pk = KeyFactory.getInstance( "DSA" ).generatePrivate( spec );
        }
        else if (pkcs8.isRSA()){
          pk = KeyFactory.getInstance( "RSA" ).generatePrivate( spec );
        }

        private_key=pk;

        certificado  = InputStreamToByteArray(isCer);
        cert = X509Certificate.getInstance(new ByteArrayInputStream(certificado));

        for (int x=15;x<35;x++)
            certNo+=(char)certificado[x];
        
        
   }


/**
 * Genera un par de llaves sin valor fiscal.
 * 
 * @param modulus
 * @param publicExponent
 * @param privateExponent
 */
   public LbsCrypt(BigInteger modulus, BigInteger publicExponent, BigInteger privateExponent){
        try {
            RSAPublicKeySpec public_ks = new RSAPublicKeySpec(modulus, publicExponent);
            RSAPrivateKeySpec private_ks = new RSAPrivateKeySpec(modulus, privateExponent);
            KeyFactory kf = KeyFactory.getInstance("RSA");
            //KeyFactory keyFactory = KeyFactory.getInstance("RSA");
                    public_key=kf.generatePublic(public_ks);
                    private_key=kf.generatePrivate(private_ks);

        } catch (InvalidKeySpecException ex) {
            Logger.getLogger(LbsCrypt.class.getName()).log(Level.SEVERE, null, ex);
        } catch (NoSuchAlgorithmException ex) {
            Logger.getLogger(LbsCrypt.class.getName()).log(Level.SEVERE, null, ex);
        }
   }

	/**
	 * Convierte un archivo del sistema a Base64
	 * 
	 * @param file
	 * @return
	 */
     public static String fileToB64 (String file) {
        File f = new File(file);
        try {
            FileInputStream fin = new FileInputStream(f);
            byte fileContent[] = new byte[(int)f.length()];
            fin.read(fileContent);
            return toB64(fileContent);

        } catch (IOException ex) {
            System.out.println("Error al cargar archivo: " + file);
            return "";
        }
    }
     
    
     /**
      * Devuelve el Certificado del CSD, en formato Base 64
      * 
      * 
      * @return
      */
     public String getB64Certificado(){
    	 return toB64(certificado);
     }


	/**
	 * Crea una llave publica, sin valor fiscal. Para pruebas.
	 * 
	 **/
	   public LbsCrypt(BigInteger modulus, BigInteger publicExponent){
	        try {
	            RSAPublicKeySpec public_ks = new RSAPublicKeySpec(modulus, publicExponent);
	            KeyFactory kf = KeyFactory.getInstance("RSA");
	            //KeyFactory keyFactory = KeyFactory.getInstance("RSA");
	                    public_key=kf.generatePublic(public_ks);
	                    private_key=null;
	        } catch (InvalidKeySpecException ex) {
	            Logger.getLogger(LbsCrypt.class.getName()).log(Level.SEVERE, null, ex);
	        } catch (NoSuchAlgorithmException ex) {
	            Logger.getLogger(LbsCrypt.class.getName()).log(Level.SEVERE, null, ex);
	        }
	   }


    public byte[] rsaEncrypt(byte[] data){
        byte[] cipherData = null;

            Cipher cipher;
			try {
				cipher = Cipher.getInstance("RSA");
	            cipher.init(Cipher.ENCRYPT_MODE, private_key);
	            cipherData = cipher.doFinal(data);
			}catch (Exception e) {
				e.printStackTrace();
			}
        return cipherData;
    }

    
    public String rsaDecrypt(byte[] data){
        byte[] cipherData = null;
        try {
            Cipher cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.DECRYPT_MODE, public_key);
            //System.out.println("asd");
            cipherData = cipher.doFinal(data);
        }catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
        return toB64(cipherData);
    }

    /**
     * Sella con: SHA1withRSA
     * 
     * 
     * @param data Cadena original a sellar.
     * 
     * @return El sello en arreglo de bytes
     */
    private byte[] sign(byte[] data) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException{
        Signature instance = Signature.getInstance("SHA1withRSA");
        instance.initSign(private_key);
        instance.update(data);
        return instance.sign();
    }

    /**
     * Digestion MD5
     * 
     * 
     * @param data Datos que se desean digerir
     * @return Digestion
     * 
     * @throws NoSuchAlgorithmException
     */
    public static String ToMD5 (byte[] data) throws NoSuchAlgorithmException{
        MessageDigest md;
        md = MessageDigest.getInstance("MD5");
        md.reset();
        md.update(data);
        return toB64(md.digest());
    }

    /**
     * Regresa la digestion MDS de un archivo.
     * 
     * @param file Ruta al archivo que se desea su digestion.
     * 
     * @return Digestion en formato MD5
     */
    public static String getMD5 (String file) {
        File f = new File(file);
        try {
            FileInputStream fin = new FileInputStream(f);
            byte fileContent[] = new byte[(int)f.length()];
            fin.read(fileContent);
            return ToMD5(fileContent);
            
        } catch (NoSuchAlgorithmException ex) {
            Logger.getLogger(LbsCrypt.class.getName()).log(Level.SEVERE, null, ex);
            return "";
        } catch (IOException ex) {
            System.out.println("Error al cargar reporte archivo" + file);
            return "";
        }
    }


    /**
     * Convierte un arrego de Bytes a Base64
     * 
     * @param data Datos a convertir.
     * @return Resultado en Base64
     */
   public static String toB64(byte[] data){
      return Base64.encode(data).toString().replaceAll("\\n", "");
   }


   /**
    * Procesa el sellado de data (la cadena original de un CFD). Aplica SHA1 despues RSA y finalmente el resultado lo convierte a Base64.
    * 
    * @param data Cadena Original
    * @return Sello en Base64
    */
   public String sellarCfd(byte[] data){
      try{
        return toB64(sign(data));
      }catch (Exception e) {
			throw new RuntimeException("Error al sellar documento.");
		}
   }


   /**
    * Metodo de pruebas para generar llaves
    * 
    */
  public void generateKeys(){
        try {
            KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
            kpg.initialize(1024);
            KeyPair kp = kpg.genKeyPair();
            public_key = kp.getPublic();
            private_key = kp.getPrivate();
            System.out.println(public_key);
            System.out.println(private_key);
        } catch (NoSuchAlgorithmException ex) {
            Logger.getLogger(LbsCrypt.class.getName()).log(Level.SEVERE, null, ex);
        }
  }


  /**
   * Verifica la vigencia del Certificado de Sello Digital
   * 
   * @return Si es o no valido el CSD.
   */
  public boolean validarCertificado(){
        if (Calendar.getInstance().getTime().after(cert.getNotAfter()))
            return false;
        else if(Calendar.getInstance().getTime().before(cert.getNotBefore()))
            return false;
        else
            return true;
  }

  /**
   * Regresa el numero de certificado del CSD;
   * 
   * @return Numero de certificado.
   */
  public String getCertNo(){
    return certNo;
  }
  
	
  /**
   *Utileria para convertir un InputStream en un ByteArray.
   *
   * @param is InputStream
   * @return Array
   * 
   * @throws IOException
   */
	public static byte[] InputStreamToByteArray(InputStream is) throws IOException{
		ByteArrayOutputStream buffer = new ByteArrayOutputStream();
		int nRead;
		byte[] data = new byte[16384];
		while ((nRead = is.read(data, 0, data.length)) != -1) {
		  buffer.write(data, 0, nRead);
		}
		buffer.flush();
		return buffer.toByteArray();
	}
}